/*
 *  Copyright (c) 2023-2025 苏州棱镜七彩信息科技有限公司 (Suzhou PrismAurora Information Technology Co.,Ltd)
 *  sbom-generate is licensed under Mulan PSL v2.
 *  You can use this software according to the terms and conditions of the Mulan PSL v2.
 *  You may obtain a copy of Mulan PSL v2 at:
 *           http://license.coscl.org.cn/MulanPSL2
 *  THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 *  See the Mulan PSL v2 for more details.
 * */
package com.ljqc;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterException;
import com.ljqc.abs.cli.CliParser;
import com.ljqc.abs.sbom.SBom;
import com.ljqc.entity.Command;
import com.ljqc.entity.DocMetaInfo;
import com.ljqc.impl.file.FileParser;
import com.ljqc.parameter.SBomParameter;
import com.ljqc.utils.ClassUtils;
import com.ljqc.utils.FileUtils;
import com.ljqc.utils.LogUtils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * SBOM生成工具入口类
 *
 * @date 2025/03/12
 */
public final class Entrance {

    private Entrance() {

    }

    private static final Logger LOGGER = Logger.getLogger(Entrance.class.getName());

    /**
     * 包管理器集合
     */
    private static final List<CliParser> CLI_PARSER_LIST = ClassUtils.getInstancesOfInterface(CliParser.class);
    private static final List<FileParser> FILE_PARSER_LIST = ClassUtils.getInstancesOfInterface(FileParser.class);

    /**
     * 文件名时间戳格式化器
     */
    private static final DateTimeFormatter FILE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    /**
     * 工具入口
     * @date 2025/3/19 16:28
     * @param args
     */
    public static void main(String[] args) {
        LOGGER.log(Level.FINE, "接收到命令行参数：{0}", String.join(" ", args));
        // 解析命令行参数
        SBomParameter sbomParameter = parseCmdArgs(args);
        if (sbomParameter == null) {
            return;
        }
        //尝试初始化命令行环境变量
        Command.initEnvironmentVariable(sbomParameter.getParams());
        //配置日志等级
        LogUtils.configureGlobalLevel(sbomParameter.getLog());
        try {
            LOGGER.log(Level.FINE, "项目根目录是否存在: {0}", sbomParameter.getRoot().exists());
            // 获取项目根目录下的所有文件
            File root = sbomParameter.getRoot();
            List<File> files = FileUtils.ls(root);
            List<Object> baseInfo;
            SBom sbomFormat = sbomParameter.getType();
            DocMetaInfo docMetaInfo = DocMetaInfo.builder().docName(sbomParameter.getDocumentName()).user(sbomParameter.getUser()).userGroup(sbomParameter.getUserGroup()).build();
            LOGGER.log(Level.INFO, "SBOM类型：{0}", sbomFormat);
            //swid比较特殊，特殊处理
            if (SBom.SWID_XML.equals(sbomFormat)) {
                LOGGER.log(Level.FINE, "使用SWID_XML格式处理文件");
                //文件清单sbom处理
                baseInfo = FILE_PARSER_LIST.stream()
                        .flatMap(fileParser -> fileParser.parser(files).stream())
                        .collect(Collectors.toList());
                LOGGER.log(Level.FINE, "文件解析完成，共解析 {0} 个文件信息", baseInfo.size());
            } else {
                LOGGER.log(Level.FINE, "使用标准格式处理文件: {0}", sbomFormat.name());
                LOGGER.log(Level.FINE, "开始使用CLI解析器处理文件");
                // 分类装填每种包管理器的特定文件
                files.forEach(file -> CLI_PARSER_LIST.parallelStream()
                        .forEach(parser -> {
                            parser.filterAdd(file, sbomFormat);
                        }));
                // 解析所有文件获取包信息
                baseInfo = CLI_PARSER_LIST.parallelStream()
                        .filter(CliParser::isNeedParser)
                        .flatMap(parser -> {
                            LOGGER.log(Level.INFO, "开始解析 {0} 的包信息", parser.getClass().getSimpleName());
                            return parser.parser().stream();
                        })
                        .collect(Collectors.toList());
                LOGGER.log(Level.FINE, "包信息解析完成，共解析 {0} 个包", baseInfo.size());
            }
            // 转换SBOM格式
            Object sbomDocument = sbomFormat.getSBomGenerate()
                    .generateByInfoList(baseInfo, docMetaInfo);
            LOGGER.log(Level.INFO, "SBOM文档对象生成成功");
            // 生成SBOM文档
            generateSbomDocument(sbomFormat, sbomDocument, sbomParameter);
            LOGGER.log(Level.INFO, "SBOM文档生成完成");
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, "生成SBOM文档失败", e);
            System.exit(1);
        }
    }

    /**
     * 解析命令行参数
     *
     * @param args 命令行参数
     * @return SBomParameter 参数对象，解析失败返回null
     */
    private static SBomParameter parseCmdArgs(String[] args) {
        LOGGER.log(Level.FINE, "开始解析命令行参数");
        LOGGER.log(Level.FINE, "原始参数：{0}", String.join(" ", args));
        SBomParameter sbomParameter = new SBomParameter();
        try {
            JCommander build = JCommander.newBuilder()
                    .addObject(sbomParameter)
                    .build();
            build.parse(args);
            LOGGER.log(Level.FINE, "命令行参数解析成功");
            LOGGER.log(Level.FINE, "解析结果：{0}", sbomParameter);
            if (sbomParameter.isHelp()) {
                ParameterException pe = new ParameterException("帮助信息");
                pe.setJCommander(build);
                pe.usage();
                return null;
            }
            return sbomParameter;
        } catch (ParameterException e) {
            LOGGER.log(Level.WARNING, "命令行参数解析异常: {0}", e.getMessage());
            e.getJCommander().usage();
        }
        return null;
    }

    /**
     * 生成SBOM文档
     *
     * @param sbomFormat    SBOM格式
     * @param sbomDocument  SBOM文档对象
     * @param sbomParameter SBOM参数
     * @throws IOException       IO异常
     * @throws TemplateException 模板处理异常
     */
    private static void generateSbomDocument(SBom sbomFormat, Object sbomDocument,
                                             SBomParameter sbomParameter) throws IOException, TemplateException {
        LOGGER.log(Level.FINE, "开始生成SBOM文档，使用格式: {0}", sbomFormat);
        // 配置FreeMarker模板
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
        LOGGER.log(Level.FINE, "初始化FreeMarker配置，版本：{0}", Configuration.VERSION_2_3_31);
        cfg.setClassLoaderForTemplateLoading(Entrance.class.getClassLoader(), "templates/sbom");
        cfg.setDefaultEncoding(StandardCharsets.UTF_8.name());
        LOGGER.log(Level.FINE, "模板目录设置为：templates/sbom，编码：{0}", StandardCharsets.UTF_8.name());

        // 处理模板
        Template template = cfg.getTemplate(sbomFormat.getTemplate());
        LOGGER.log(Level.FINE, "加载模板文件：{0}", sbomFormat.getTemplate());
        StringWriter writer = new StringWriter();
        template.process(sbomDocument, writer);
        String output = writer.toString();
        LOGGER.log(Level.FINE, "模板处理完成，生成文档大小：{0} 字节", output.length());

        // 输出SBOM文档
        outputSbomDocument(output, sbomParameter, sbomFormat);
    }

    /**
     * 输出SBOM文档
     *
     * @param content       文档内容
     * @param sbomParameter SBOM参数
     * @param sbomFormat    SBOM格式
     * @throws IOException IO异常
     */
    private static void outputSbomDocument(String content, SBomParameter sbomParameter,
                                           SBom sbomFormat) throws IOException {
        File outputFile = sbomParameter.getOut();
        if (outputFile == null) {
            LOGGER.log(Level.INFO, "未指定输出文件，将输出到控制台");
            System.out.println(content);
            return;
        }

        // 生成文件名
        String fileName = generateFileName(sbomParameter, sbomFormat);
        Path outputPath = Paths.get(outputFile.getPath(), fileName);
        LOGGER.log(Level.INFO, "准备写入文件，路径: {0}", outputPath);

        // 确保父目录存在并写入文件
        try {
            Files.createDirectories(outputPath.getParent());
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, "结果文件检查异常: " + outputPath, e);
        }
        Files.write(outputPath, content.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 生成输出文件名
     *
     * @param sbomParameter SBOM参数
     * @param sbomFormat    SBOM格式
     * @return 生成的文件名
     */
    private static String generateFileName(SBomParameter sbomParameter, SBom sbomFormat) {
        String baseName;
        if (sbomParameter.getDocumentName() != null && !sbomParameter.getDocumentName().trim().isEmpty()) {
            baseName = sbomParameter.getDocumentName();
        } else {
            String timestamp = LocalDateTime.now().format(FILE_TIME_FORMATTER);
            baseName = String.format("SBOM-%s-%s", UUID.randomUUID(), timestamp);
        }
        return baseName + sbomFormat.getSuffix();
    }
}
